/*
 * EmisorAritmetico.cpp
 *
 *  Created on: 06/04/2012
 *      Author: lucas
 */

#include "../hdr/EmisorAritmetico.h"
#include <iostream>
#include <stdio.h>
#include "../hdr/FuncionesAritmeticas.h"

using namespace std;

unsigned int EmisorAritmetico::tamanioBuffer = 1024;
unsigned int EmisorAritmetico::precision = 16;

EmisorAritmetico::EmisorAritmetico(FILE* archivo) :
		archivo(archivo), piso(0), techo((1 << precision) - 1), limite(
				1 << (precision - 1)), underflow(0), subBuffer(0), contadorSubBuffer(
				0), contadorBuffer(0) {
	buffer = new unsigned char[tamanioBuffer];
	proximoCaracter = buffer;
	archivoDebug = fopen("debugWrite.dbg", "wb");
}

EmisorAritmetico::~EmisorAritmetico() {
	fclose(archivoDebug);
	delete[] buffer;
}

void EmisorAritmetico::imprimirNumero(unsigned int n) {
	for (int i = 8 * sizeof(unsigned int) - 1; i >= 0; i--) {
		cout << ((n & (1 << i)) >> i);
		if (i % 4 == 0)
			cout << " ";
	}
	n |= subBuffer << precision;
	cout << (n / (1.0 * ((1 << (precision + contadorSubBuffer)) + 1))) << endl;
}

// Renormaliza el piso y el techo actual, emitiendo los bits eliminados
void EmisorAritmetico::renormalizar() {
	// Renormaliza los bits en común y los emite junto al underflow que
	// estaba calculado hasta el momento
	emitirBits();
	// Elimina bits de underflow y actualiza el mismo
	underflow += FuncionesAritmeticas::actualizarUnderflow(piso, techo,
			precision);
}

void EmisorAritmetico::finalizarEmision() {
	this->actualizarBuffer(1, 1);
	if (contadorSubBuffer > 0) {
		*proximoCaracter = (unsigned char) (subBuffer
				<< (8 * sizeof(char) - contadorSubBuffer));
		contadorSubBuffer = 0;
		contadorBuffer++;
//		fprintf(stdout, "%.2x  ", *proximoCaracter);
	}
	this->escribirBufferEnArchivo();
}

// Emite un símbolo: toma el rango (0, total), y lo achica a (piso, piso+largo).
// Piso+largo debe ser menor o igual al total, piso >= 0 y largo > 0.
void EmisorAritmetico::emitirSimbolo(unsigned short piso, unsigned short largo,
		unsigned short total) {
//	Archivo de debug (no afecta).
//	fwrite(&this->piso, sizeof(unsigned int), 1, archivoDebug);
//	fwrite(&this->techo, sizeof(unsigned int), 1, archivoDebug);
	//fprintf(archivoDebug, "%d / %d (%d)\n", largo, total, piso);
	// A partir de los parámetros, reconstruye el techo y el piso del emisor
	// (es simplemente un cambio de escala).
	unsigned int largo_actual = this->techo - this->piso + 1;
	this->techo = this->piso + (largo_actual * (piso + largo)) / total - 1;
	this->piso += (largo_actual * piso) / total;
	this->renormalizar();
}

// Actualiza el buffer de bits emitidos con la cantidad y bits recibidos.
void EmisorAritmetico::actualizarBuffer(unsigned int bitsAEmitir,
		unsigned int cantidad) {
	subBuffer <<= cantidad; // Hacemos espacio para los nuevos bits
	subBuffer |= bitsAEmitir; // Copiamos los bits
	contadorSubBuffer += cantidad; // El subBuffer ahora tiene más bits
	// Vemos si tenemos suficientes bits para formar un byte, y en ese caso
	// los tranferimos del subBuffer al buffer.
	while (contadorSubBuffer >= 8 * sizeof(char)) {
		// proximoCaracter agarra los 8 primeros bits del subBuffer
		*proximoCaracter = (unsigned char) (subBuffer
				>> (contadorSubBuffer - 8 * sizeof(char)));
		contadorSubBuffer -= 8 * sizeof(char); // Ahora tenemos 8 bits menos
		proximoCaracter++;
		contadorBuffer++;
		if (contadorBuffer >= tamanioBuffer) {
			this->escribirBufferEnArchivo();
		}
	}
}

// Escribe el buffer de bits emitidos al archivo
int EmisorAritmetico::escribirBufferEnArchivo() {
	if (fwrite(buffer, sizeof(char), contadorBuffer, archivo) != contadorBuffer)
		return -1;
	contadorBuffer = 0;
	proximoCaracter = buffer;
	return 0;
}

unsigned int EmisorAritmetico::emitirBits() {
	unsigned int cantBitsEmitidos = 0;
	// limite = 0.5 (si el intervalo total es (0, 2^n) => limite = 2^(n-1) ),
	// donde n es la precisión.
	unsigned int limite = 1 << (precision - 1);
	unsigned int emision;
	// Vemos si techo y piso coinciden en el primer bit
	if (techo < limite || piso >= limite) {
		// Si hay underflow emite el bit + todos los bits de underflow
		if (underflow > 0) {
			// Copia el bit en común a emision y lo manda al buffer
			emision = (techo < limite) ? 0 : 1;
			actualizarBuffer(emision, 1);
			while (underflow > precision) {
				// Copia precision bits de underflow y los manda al buffer
				emision = (techo < limite) ? ((1 << precision) - 1) : 0;
				actualizarBuffer(emision, precision);
				underflow -= precision;
			}
			// Copia el resto de bits de underflow y los manda al buffer
			emision = (techo < limite) ? ((1 << underflow) - 1) : 0;
			actualizarBuffer(emision, underflow);
			underflow = 0;
			// Elimina el primer bit en común
			FuncionesAritmeticas::sacarPrimerBit(piso, precision, 0);
			FuncionesAritmeticas::sacarPrimerBit(techo, precision, 1);
		}
	}
	emision = 0;
	// Vemos si techo y piso coinciden en el primer bit (ahora no hay underflow)
	while (techo < limite || piso >= limite) {
		cantBitsEmitidos++;
		emision <<= 1; // hace espacio para el nuevo bit
		emision |= (techo < limite) ? 0 : 1; // Copia el bit en común
		// Elimina el bit en común
		FuncionesAritmeticas::sacarPrimerBit(piso, precision, 0);
		FuncionesAritmeticas::sacarPrimerBit(techo, precision, 1);
	}
	actualizarBuffer(emision, cantBitsEmitidos);
	return cantBitsEmitidos;
}
